package naito_rescue.communication.manager;

import java.util.*;
import java.util.Map.Entry;
import sample.*;
import rescuecore2.messages.*;
import rescuecore2.worldmodel.*;
import rescuecore2.Constants;
import rescuecore2.config.*;
import rescuecore2.standard.components.*;
import rescuecore2.standard.entities.*;
import rescuecore2.standard.messages.*;
import rescuecore2.misc.geometry.*;
import org.apache.commons.lang.RandomStringUtils;

import naito_rescue.*;
import naito_rescue.agent.*;
import naito_rescue.communication.team.*;
import naito_rescue.communication.channel.*;
import naito_rescue.communication.module.*;
import naito_rescue.object.*;
import naito_rescue.message.*;
import naito_rescue.message.manager.*;

public class NAITOCommunicationManager
{
	private ChannelManager      channelManager;
	private NAITOAgent          owner;
	private StandardWorldModel  model;
	private MyLogger            logger;
	private Config              config;
	private CommunicationModule commModule;
	private List<NAITOMessage>  hasForwarded;
	//accumulateされて，flushAccumulatedMessagesで送信される
	private List<NAITOMessage>  accumulates;
	private Map<Integer, List<NAITOMessage>> receiveHistory;
	
	public NAITOCommunicationManager(NAITOAgent owner, Config config){
		this.owner = owner;
		this.config = config;
		this.model = owner.getWorldModel();
		this.logger = owner.getLogger();
		this.channelManager = new ChannelManager(this.owner, this.config);
		this.commModule = new CommunicationModule(this.owner);
		this.accumulates = new ArrayList<NAITOMessage>();
		this.receiveHistory = new HashMap<Integer, List<NAITOMessage>>();
		this.hasForwarded = new ArrayList<NAITOMessage>();
	}
	public void accumulateMessages (List<? extends NAITOMessage> messages){
		this.accumulates.addAll(messages);
	}
	public void accumulateMessage(NAITOMessage message){
		this.accumulateMessages(Arrays.asList(message));
		//logger.debug("Accumulated Message = " + message, "accumulateMessage");
	}
	// 蓄積されたメッセージを、直接通信にて送信する
	public void flushMessagesToVoice(){
		VoiceChannel voiceChannel = channelManager.getVoiceChannel();
		
		// IDの生成
		for(NAITOMessage message : this.accumulates){
			if(message instanceof NAITOBaseMessage){
				generateBaseMessageID((NAITOBaseMessage)message);
			}
		}
		VoiceChannel channel = channelManager.getVoiceChannel();
		commModule.sendMessages(this.accumulates, channel, channel.getRemaindBandwidth(owner.getTime()), voiceChannel);
		
		if(this.accumulates.isEmpty()){
			// All messages are sent.
		}
	}
	public void flushMessages(){
		String context = "flushMessages";
		if(!isMyTurn()){
			//logger.info("This time(" + owner.getTime() + ") is not my turn.",context);
			return;
		}
		//Channel <=> Channelのマッピングを作る
		Map<AbstractChannel, List<NAITOMessage>> maps
			= this.channelManager.mapChannelToMessages(this.accumulates);
		for(Entry<AbstractChannel, List<NAITOMessage>> entry : maps.entrySet()){
			AbstractChannel channel = entry.getKey();
			List<NAITOMessage> messages = entry.getValue();
			
			//logger.info("To channel " + channel.getChannelNum() + ", will send " + messages.size() + " messages.",context);
			//logger.debug("Messages are = " + messages, context);
			
			for(NAITOMessage message : messages){
				if(message instanceof NAITOBaseMessage){
					generateBaseMessageID((NAITOBaseMessage)message);
				}
			}
			int maxBandwidth = channelManager.getBandwidthPerAgent();
			//送信
			commModule.sendMessages(messages, channel, maxBandwidth, channelManager.getVoiceChannel());
		}
		//送信済みメッセージの削除
		Iterator<NAITOMessage> iterator = this.accumulates.iterator();
		while(iterator.hasNext()){
			NAITOMessage message = iterator.next();
			if(message.hasSent()){
				//logger.debug("Message has sent remove.", "NAITOCommunicaitonManager#flushMessages");
				iterator.remove();
			}
		}
		
	}
	private void generateBaseMessageID(NAITOBaseMessage bm){
		// 3桁のランダムな数字列
		String idSeed = RandomStringUtils.random(1, "123456789") + RandomStringUtils.randomNumeric(2);
		//エージェントのIDの数字列と，その下位3桁にランダムな数字列を差し込んだものをIDとする
		bm.setID(owner.getID().getValue() * 1000 + Integer.parseInt(idSeed));
	}
	
	public void receiveMessages(Collection<Command> commands, int time){
		if(receiveHistory.get(time) == null){
			receiveHistory.put(time, new ArrayList<NAITOMessage>());
		}
		List<NAITOMessage> history = receiveHistory.get(time);
		EntityID selfID = owner.getID();
		for(Command command : commands){
			if(command instanceof AKSpeak){
				AKSpeak speak = (AKSpeak)command;
				//senderと同じIDのAKSpeakは無視
				if(selfID.getValue() == speak.getAgentID().getValue()){
					continue;
				}
				List<NAITOMessage> received = commModule.receiveMessages(speak, channelManager.getVoiceChannel()); 
				history.addAll(received);
			}
		}
		
		//debugprint.
		/*
		for(NAITOMessage receive : receiveHistory.get(time)){
			int id = -1;
			if(receive instanceof NAITOBaseMessage){
				NAITOBaseMessage bm = (NAITOBaseMessage)receive;
				id = bm.getID();
			}
			//logger.debug("Received message = " + receive + " (ID=" + id + "), time=" + time, "receiveMessage");
		}
		*/
		//forward
		for(NAITOMessage receive : receiveHistory.get(time)){
			if(!hasForwarded.contains(receive)){
				hasForwarded.add(receive);
			}
		}
		//logger.info("Received " + receiveHistory.get(time).size() + " messages.", "receiveMessages");
	}
	
	public void subscribeMyChannel(){
		AbstractChannel myChannel = channelManager.getMyChannel();
		//本当は例外を吐かせるべき?
		if(myChannel == null){
			//logger.info("NAITOCommunicationManager#subscribeMyChannel() has failed.");
			return;
		}
		owner.subscribe(myChannel.getChannelNum());
		//logger.info("Subscribed channel is = " + myChannel.getChannelNum(), "subscribeMyChannel");
	}
	public List<NAITOMessage> getMessages(int time){
		//logger.info("Get " + (receiveHistory.get(time)==null?"0":receiveHistory.get(time).size()) + " messages. ","NAITOCommunicationManager#getMessages");
		return receiveHistory.get(time);
	}
	public boolean isMyTurn(){
		StandardEntity sender = this.owner.getMe();
		int            timestep = this.owner.getTime();
		return channelManager.isMyTurn(sender, timestep);
	}
	public int getBandwidth(){
		return channelManager.getBandwidthPerAgent();
	}
}
